package cn.gs.algorithm.graph;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author gs
 */
public class WeightedGraph<T, V, E> extends Graph<T> {

    Map<T, V> nodes = new HashMap<T, V>();

    public Map<T, V> valuedNodes() {
        return Collections.unmodifiableMap(nodes);
    }

    public Map<T, E> weightedOuters(T node) {
        return Collections.unmodifiableMap(addNode(node).outweights);
    }

    public Map<T, E> weightedInners(T node) {
        return Collections.unmodifiableMap(addNode(node).inweights);
    }

    public V setNodeValue(T node, V value) {
        return nodes.put(node, value);
    }

    public V nodeValue(T node) {
        return nodes.get(node);
    }

    public E setEdgeValue(T outer, T inner, E weight) {
        setOutEdgeValue(outer, inner, weight);
        setInEdgeValue(inner, outer, weight);
        return weight;
    }

    public E setOutEdgeValue(T outer, T inner, E weight) {
        WeightedLinkSet<T, E> w = addNode(outer);
        w.addOutLink(inner);
        w.outweights.put(inner, weight);
        w = addNode(inner);
        w.addInLink(outer);
        w.inweights.put(outer, weight);
        return weight;
    }

    public E setInEdgeValue(T outer, T inner, E weight) {
        return setOutEdgeValue(inner, outer, weight);
    }

    public E edgeWeight(T outer, T inner) {
        return outEdgeWeight(outer, inner);
    }

    public E outEdgeWeight(T outer, T inner) {
        WeightedLinkSet<T, E> w = addNode(outer);
        return w.outweights.get(inner);
    }

    public E inEdgeWeight(T outer, T inner) {
        return outEdgeWeight(inner, outer);
    }

    @Override
    public WeightedLinkSet<T, E> addNode(T key) {
        LinkSet<T> set = links.get(key);
        if (set == null) {
            WeightedLinkSet<T, E> st = new WeightedLinkSet<T, E>();
            links.put(key, st);
            return st;
        } else if (!(set instanceof WeightedLinkSet)) {
            WeightedLinkSet<T, E> st = new WeightedLinkSet<T, E>();
            st.copyFrom(set);
            links.put(key, st);
            return st;
        } else {
            return (WeightedLinkSet<T, E>) set;
        }
    }

    public boolean addBiLink(T p, T q, E e) {
        boolean b = true;
        b &= addOutLink(p, q, e);
        b &= addInLink(p, q, e);
        return b;
    }

    public boolean addOutLink(T p, T q, E e) {
        WeightedLinkSet<T, E> x = addNode(p);
        WeightedLinkSet<T, E> y = addNode(q);
        return x.addOutLink(q, e) & y.addInLink(p, e);
    }

    public boolean addInLink(T p, T q, E e) {
        return addOutLink(q, p, e);
    }

    public static class WeightedLinkSet<T, E> extends LinkSet<T> {

        Map<T, E> outweights = new HashMap<T, E>();
        Map<T, E> inweights = new HashMap<T, E>();

        boolean addOutLink(T node, E e) {
            outweights.put(node, e);
            return outer.add(node);
        }

        boolean addInLink(T node, E e) {
            inweights.put(node, e);
            return inner.add(node);
        }

        boolean delOutLink(T node) {
            outweights.remove(node);
            return outer.remove(node);
        }

        boolean delInLink(T node) {
            inweights.remove(node);
            return inner.remove(node);
        }

        void copyFrom(LinkSet<T> link) {
            outer.clear();
            outer.addAll(link.outer);
            inner.clear();
            inner.addAll(link.inner);
        }
    }
}
